package intopo.game.impl.ipd;

import intopo.evogame.partnerselection.config.ParsingException;
import java.io.IOException;
import org.w3c.dom.Element;
import cern.jet.random.engine.RandomEngine;

import intopo.evogame.partnerselection.Cache;
import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Scanner;

import util.xml.*;

import intopo.game.strategy.ConstantStrategyParameter;
import intopo.game.strategy.StrategyParameter;
import intopo.util.Util;

/**
 * This class provides an implementation of an Iterated Prisoner's Dilemma
 * player whose actions are determined via a lookup table. The size of this
 * table depends on the past history the player tracks. If the history size
 * is 1, then the player will act depending on the actions played by its
 * partner on the last iteration. The table contains floating point values
 * that represent the probability of the player cooperating.

 * @author <a href="mailto:plsm@det.ua.pt">Pedro Lopes da Silva Mariano</a>

 * @version 1.1
 */
final public class IteratedPrisonerDilemmaProbabilisticStrategy
	extends IteratedPrisonerDilemmaStrategy
{
	/**
	 *
	 */
	static String CONSTRUCTOR_TAG = "IteratedPrisonerDilemmaProbabilisticStrategy";
	/**
	 *
	 */
	final private float [][]lookupTable;
	/**
	 *
	 */
	final private int historySize;
	/**
	 *
	 * @param cooperateProbability
	 * @param historySize
	 */
	public IteratedPrisonerDilemmaProbabilisticStrategy (float cooperateProbability, int historySize)
	{
		assert historySize >= 0;
		this.historySize = historySize;
		lookupTable = new float [historySize + 1][];
		for (int i = 0; i < historySize + 1; i++) {
			lookupTable [i] = new float [(int) Math.pow (2, i)];
			Arrays.fill (lookupTable [i], cooperateProbability);
		}
	}
	/**
	 *
	 * @param cooperateFirstIteration
	 * @param cooperateIfDefected
	 * @param cooperateIfCooperated
	 */
	public IteratedPrisonerDilemmaProbabilisticStrategy (float cooperateFirstIteration, float cooperateIfDefected, float cooperateIfCooperated)
	{
		historySize = 1;
		lookupTable = new float [2][];
		lookupTable [0] = new float [1];
		lookupTable [0][0] = cooperateFirstIteration;
		lookupTable [1] = new float [2];
		lookupTable [1][0] = cooperateIfDefected;
		lookupTable [1][1] = cooperateIfCooperated;
	}

	/**
	 * Construct a new {@code GiveTakeProbabilisticPlayer} whose fields are
	 * initialized by searching the DOM element for specific child nodes.

	 * <p>The XML document must have the following tags and attributes:
	 * <pre>
	 *  &lt;cooperateFirstIteration value = "FLOAT" /&gt;
	 *  &lt;cooperateIfDefected value = "FLOAT" /&gt;
	 *  &lt;cooperateIfCooperated value = "FLOAT" /&gt;
	 * </pre>
	 */
	public IteratedPrisonerDilemmaProbabilisticStrategy (Element element)
		throws XMLParseException
	{
		AttributeParser<Float> apProb = new AttributeParser<Float>
			(new CheckExistenceAttribute<Float> (),
			 new ParseAsRandomFloat (),
			 new ParseAsFloat (0, 1));
		historySize = 1;
		lookupTable = new float [2][];
		lookupTable [0] = new float [1];
		lookupTable [0][0] = apProb.parse (element, "CooperateFirstIteration", "value");
		lookupTable [1] = new float [2];
		lookupTable [1][0] = apProb.parse (element, "CooperateIfDefected", "value");
		lookupTable [1][1] = apProb.parse (element, "CooperateIfCooperated", "value");
	}

	/**
	 * Reads a strategy for the iteraded prisoner's dilemma from the given scanner.  The scanner must have three floats: one representing the probability to cooperate in the first round; the second represents the
	 * the probability to cooperate if the partner defected in the last round; the third represents the probability to cooperate
	 * if the partner cooperated in the last round.
	 */
	public IteratedPrisonerDilemmaProbabilisticStrategy (Scanner scanner)
	{
		historySize = 1;
		lookupTable = new float [2][];
		lookupTable [0] = new float [1];
		lookupTable [0][0] = scanner.nextFloat ();
		lookupTable [1] = new float [2];
		lookupTable [1][0] = scanner.nextFloat ();
		lookupTable [1][1] = scanner.nextFloat ();
	}

	private IteratedPrisonerDilemmaProbabilisticStrategy (float [][]lookupTable, int historySize)
	{
		this.lookupTable = lookupTable;
		this.historySize = historySize;
	}

	public IteratedPrisonerDilemmaStrategy clone ()
	{
		return new IteratedPrisonerDilemmaProbabilisticStrategy (lookupTable, historySize);
	}

	public float cooperateFirstIteration ()
	{
		return lookupTable [0][0];
	}

	public float cooperateIfDefected ()
	{
		return lookupTable [1][0];
	}
	public float cooperateIfCooperated ()
	{
		return lookupTable [1][1];
	}


	/**
	 * Returns the number of parameters this player's strategy has. Strategy
	 * parameters are referred by an index that ranges from 0 to the value
	 * returned by this method minus 1.
	 */
	public int numberParameters ()
	{
		int result = 0;
		for (float[] h : lookupTable) {
			result += h.length;
		}
		return result;
	}

	/**
	 * Returns this strategy parameter at the given index.

	 * @param index The index of the parameter to return.

	 * @throws IndexOutOfBoundsException If the index is out of bounds.
	 */
	public StrategyParameter getParameter (int index)
	{
		for (float[] h : lookupTable) {
			if (h.length <= index) {
				index -= h.length;
			}
			else {
				return new ConstantStrategyParameter<Float> ("", h [index]);
			}
		}
		throw new IndexOutOfBoundsException ();
	}

	/**
	 * Returns the name of this strategy parameter at the given index. This
	 * name should be a short description to be used in statistical
	 * information.

	 * @param index The index of the parameter whose name is to be to
	 * returned.

	 * @throws IndexOutOfBoundsException If the index is out of bounds.
	 */
	public String getParameterName (int index)
	{
		for (float[] h : lookupTable) {
			if (h.length <= index) {
				index -= h.length;
			}
			else {
				int size = 0;
				int length = h.length;
				do {
					size++;
					length = length >> 1;
				} while (length > 0);
				char[] result = new char [size];
				for (int i = 0; i < size; i++) {
					if (index % 2 == 0) {
						result [i] = 'C';
					}
					else {
						result [i] = 'D';
					}
					index = index >> 1;
				}
				return new String (result);
			}
		}
		throw new IndexOutOfBoundsException ();
	}

	public Object getParameterValue (int index)
	{
		for (float[] h : lookupTable) {
			if (h.length <= index) {
				index -= h.length;
			}
			else {
				return h [index];
			}
		}
		throw new IndexOutOfBoundsException ();
	}

	public void setParameterValue (int index, Object newValue)
	{
		if (!(newValue instanceof Number))
			throw new ClassCastException ();
		for (float[] h : lookupTable) {
			if (h.length <= index) {
				index -= h.length;
			}
			else {
				h [index] = ((Number) newValue).floatValue ();
				return ;
			}
		}
		throw new IndexOutOfBoundsException ();
	}

	public boolean cooperate (IteratedPrisonerDilemmaHistory history, int index)
	{
		int lookupFirstIndex = 0, lookupSecondIndex = 0;
		for (int i = 0; i < historySize && i / 2 < history.getNumberOfIterations (); i++) {
			lookupFirstIndex++;
			lookupSecondIndex *= 2;
			boolean c;
			if (i % 2 == 0)
				c = history.cooperatedInPastIteration (i / 2 + 1, 1 - index);
			else
				c = history.cooperatedInPastIteration (i / 2 + 1, index);
			if (c)
				lookupSecondIndex++;
		}
		return Cache.supply.nextDouble () <= lookupTable [lookupFirstIndex][lookupSecondIndex];
	}

	public int getBin ()
	{
		int bin = 0;
		int i, j;
		for (i = 0; i < lookupTable.length; i++) {
			for (j = 0; j < lookupTable [i].length; i++) {
				float p = lookupTable [i][j];
				int r = (int) Math.floor (p * Parameters.probabilityBin);
				if (r == Parameters.probabilityBin) {
					r--;
				}
				bin *= Parameters.probabilityBin;
				bin += r;
			}
		}
		return bin;
	}

	static String getBinDescription (int bin)
	{
		float p0, pC, pD;
		p0 = (bin % Parameters.probabilityBin) / (float) Parameters.probabilityBin;
		bin /= Parameters.probabilityBin;
		pC = (bin % Parameters.probabilityBin) / (float) Parameters.probabilityBin;
		bin /= Parameters.probabilityBin;
		pD = (bin % Parameters.probabilityBin) / (float) Parameters.probabilityBin;
		StringBuffer result = new StringBuffer (3 * 10 + 2);
		result.append (p0);
		result.append (' ');
		result.append (pC);
		result.append (' ');
		result.append (pD);;
		return result.toString ();
	}


	public boolean isCooperator ()
	{
		return this.lookupTable [0][0] > 0.9;
	}
	
	public IteratedPrisonerDilemmaStrategy getMutantAllGenes ()
	{
		float [][] lookupTable = new float [this.lookupTable.length][];
		int i, j;
		for (i = 0; i < lookupTable.length; i++) {
			lookupTable [i] = new float [this.lookupTable [i].length];
			for (j = 0; j < lookupTable [i].length; i++) {
				float p = this.lookupTable [i][j];
				p += new cern.jet.random.Normal (0.0, 0.1, Cache.supply).nextDouble ();
				p = Math.max (0.0f, Math.min (p, 1.0f));
				lookupTable [i][j] = p;
			}
		}
		return new IteratedPrisonerDilemmaProbabilisticStrategy (lookupTable, historySize);
	}

	public IteratedPrisonerDilemmaStrategy getMutantSingleGene ()
	{
		float [][] lookupTable = new float [this.lookupTable.length][];
		int i, j;
		final int gene = Util.nextPositiveInt (this.numberParameters ());
		int count = 0;
		for (i = 0; i < lookupTable.length; i++) {
			lookupTable [i] = new float [this.lookupTable [i].length];
			for (j = 0; j < lookupTable [i].length; i++) {
				float p = this.lookupTable [i][j];
				if (count == gene) {
					lookupTable [i][j] = Util.perturbNormal (p, Parameters.probStddev, 0, 1);
				}
				else {
					lookupTable [i][j] = p;
				}
				count++;
			}
		}
		return new IteratedPrisonerDilemmaProbabilisticStrategy (lookupTable, historySize);
	}
	/**
	 * 
	 * @param ps
	 */
	public void print (PrintStream ps)
	{
		ps.print (CONSTRUCTOR_TAG);
		ps.print (' ');
		ps.print (lookupTable [0][0]);
		ps.print (' ');
		ps.print (lookupTable [1][0]);
		ps.print (' ');
		ps.print (lookupTable [1][1]);
	}
	public void debug (PrintStream ps)
	{
		ps.print (historySize);
		for (int i = 0; i < lookupTable.length; i++) {
			for (int j = 0; j < lookupTable[i].length; j++) {
				ps.print (' ');
				ps.print (lookupTable[i][j]);
			}
		}
	}
	/**
	 * Write this strategy to the given stream.
	 * <p> The data format is:
	 * <pre>
	 * | byte 1 | byte 2 | byte 3 | byte 4 | byte 5 | byte 6 |
	 * |   history size  |       cooperate probability       |
	 * |                 |                                   |
	 * | byte 7 | byte 8 | byte 9 | byte 10|...
	 * |       cooperate probability       |
	 * |                                   |
	 * </pre>
	 * @param dos
	 * @throws java.io.IOException
	 */
	public void write (java.io.DataOutputStream dos)
		throws IOException
	{
		dos.writeInt (this.historySize);
		for (int i = 0; i < this.lookupTable.length; i++) {
			for (int j = 0; j < this.lookupTable[i].length; j++) {
				dos.writeFloat (this.lookupTable[i][j]);
			}
		}
	}
//	static public class BinParameters
//	{
//		static public int probabilityX = 3;
//	}
	static public class Parameters
	{
		/**
		 * Used in calculating the <i>bin</i> of the enclosing strategy.
		 */
		static public int probabilityBin;
		/**
		 * Standard deviation of the normal distribution used to mutate the probability to cooperate of the enclosing strategy.  This distribution is used to mutate the probabilities for every history the strategy considers.
		 */
		static public float probStddev = 0.1f;
		/**
		 * Reads the global parameter that effec tthe behaviour of the enclosing class.
		 * @param scanner
		 */
		static public void read (Scanner scanner)
			throws ParsingException
		{
			Parameters.probabilityBin = AbstractTextConfigFile.readInt (scanner, "Number of bins a probability is converted");
			Parameters.probStddev = AbstractTextConfigFile.readFloat (scanner, "Standard deviation of the normal distribution used to mutate the probability to cooperate");
		}

		static void write (PrintStream ps)
		{
			ps.print (Parameters.probabilityBin);
			ps.println ("\t# Number of bins a probability is converted");
			ps.print (Parameters.probStddev);
			ps.println ("\t# Standard deviation of the normal distribution used to mutate the probability to cooperate");
		}
	}
}
